package com.javacoo.xservice.base.interceptor;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import com.javacoo.xservice.base.BaseRequest;
import com.javacoo.xservice.base.BaseResponse;
import com.javacoo.xservice.base.ErrorCodeConstants;
import com.javacoo.xservice.base.config.Resources;
import com.javacoo.xservice.base.exception.BaseException;
import com.javacoo.xservice.base.exception.IllegalParameterException;
import com.javacoo.xservice.base.setting.AppCoreSetting;
import com.javacoo.xservice.base.support.RequestType;
import com.javacoo.xservice.base.support.event.ApplicationContextProvider;
import com.javacoo.xservice.base.support.event.TransCompleteEvent;
import com.javacoo.xservice.base.support.handler.HandlerStack;
import com.javacoo.xservice.base.support.swaparea.SwapAreaData;
import com.javacoo.xservice.base.support.swaparea.SwapAreaUtils;
import com.javacoo.xservice.base.utils.FastJsonUtil;
import com.javacoo.xservice.base.utils.WebUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * 处理拦截器
 * <p>
 * 说明:
 * </p>
 * <li></li>
 * @author DuanYong
 * @since 2017年6月28日上午9:53:58
 */
@Slf4j
public class RequestHandlerInterceptor extends BaseInterceptor {
	/**请求参数签名KEY*/
	private static final String SIGN_KEY = "sign";
	/**请求时间戳KEY*/
	private static final String TIMESTAMP_KEY = "timestamp";
    /**请求流水号*/
    private static final String TRANS_SN = "transactionSn";

	@Autowired
	private AppCoreSetting appCoreSetting;
    /**处理器链*/
    @Autowired
    private HandlerStack handlerStack;
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		//初始化数据交换区
		initSwapArea(request);
		// options ，则不处理
		if (RequestType.OPTIONS.value().equals(request.getMethod().toLowerCase())) {
			return false;
		}
		try {
			doPreHandle(request);
		} catch (Exception ex) {
			handleException(response, ex);
			return false;
		}
		return super.preHandle(request, response, handler);
	}

	/**
	 * 执行预处理
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param request
	 * @throws IOException
	 * @throws UnsupportedEncodingException
	 * @since 2017年6月30日上午9:36:10
	 */
	private void doPreHandle(HttpServletRequest request)
			throws IOException, UnsupportedEncodingException, ServletException {
		// 接口只能接受post请求
		if (!RequestType.POST.value().equals(request.getMethod().toLowerCase())) {
			throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_POST));
		}
		//解析请求参数
		BaseRequest baseRequest;
		//如果是带文件的请求
		if(request instanceof MultipartHttpServletRequest){
			baseRequest = parserFile(request);
		}else{
			baseRequest = parser(request);
		}
		//预处理
		preProcess(baseRequest);
	}
	/**
	 * 解析带文件的请求
	 * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/6/11 11:13
	 * @param request: 原始请求参数
	 * @return: com.jccfc.service.base.BaseRequest
	 */
	private BaseRequest parserFile(HttpServletRequest request) throws IOException, ServletException {
		log.info("解析带文件的请求");
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
		//获取附带参数
		Part part = multipartRequest.getPart(AppCoreSetting.DEFAULT_UPLOAD_FILE_PARAM_KEY);
		if(null == part){
			throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_UPLOAD_FILE_PARAM_ERROR,AppCoreSetting.DEFAULT_UPLOAD_FILE_PARAM_KEY));
		}
		//校验附件类型
		checkFileType(multipartRequest);
		//解析 Part
		String json = streamToString(part.getInputStream());
		BaseRequest baseRequest = getBaseRequest(request,json);
		baseRequest.setFiles(multipartRequest.getFileMap());
		return baseRequest;
	}
	/**
	 * inputStream转String
	 * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/6/11 11:24
	 * @param inputStream:
	 * @return: java.lang.String
	 */
	private String streamToString(InputStream inputStream) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
		StreamUtils.copy(inputStream, out);
		return new String(out.toByteArray(), "utf-8");
	}

	/**
	 * 校验文件类型
	 * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/6/11 11:10
	 * @param multipartRequest:
	 * @return: void
	 */
	private void checkFileType(MultipartHttpServletRequest multipartRequest){
		if(multipartRequest.getFileMap() == null || multipartRequest.getFileMap().isEmpty()){
			return;
		}
		//校验附件类型
		Map<String, MultipartFile> files = multipartRequest.getFileMap();
		files.remove(AppCoreSetting.DEFAULT_UPLOAD_FILE_PARAM_KEY);
		Iterator<String> iterator = files.keySet().iterator();
		//对多部件请求资源进行遍历
		while (iterator.hasNext()) {
			MultipartFile multipartFile = multipartRequest.getFile(iterator.next());
			String filename = multipartFile.getOriginalFilename();
			//判断是否为限制文件类型
			if (!checkFile(filename)) {
				throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_UPLOAD_FILE_TYPE_ERROR,appCoreSetting.getSuffixList()));
			}
		}
	}
	/**
	 * 检查文件类型
	 * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/6/10 16:13
	 * @param fileName: 文件名
	 * @return: boolean 是否通过
	 */
	private boolean checkFile(String fileName) {
		//设置允许上传文件类型
		String suffixList = appCoreSetting.getSuffixList();
		// 获取文件后缀
		String suffix = FilenameUtils.getExtension(fileName);
		log.info("检查文件类型:{},允许的文件类型:{}",fileName,suffixList);
		if (StringUtils.isNotBlank(suffix) && suffixList.contains(suffix.trim().toLowerCase())) {
			return true;
		}
		return false;
	}
    /**
     * 预处理
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param baseRequest
     * @since 2017年11月2日上午8:53:27
     */
	private void preProcess(BaseRequest baseRequest) {
		Long startTime = System.currentTimeMillis();
		//如果未传入交易流水号，则生成
		if(StringUtils.isBlank(baseRequest.getTransactionSn())){
			baseRequest.setTransactionSn(SwapAreaUtils.getSwapAreaData().getTransactionSn());
		}else{
			SwapAreaUtils.getSwapAreaData().setTransactionSn(baseRequest.getTransactionSn());
		}
        //前置处理
        handlerStack.doPreStack(baseRequest);
        //设置请求对象
		SwapAreaUtils.getSwapAreaData().setBaseRequest(baseRequest);
		log.info("[{}]接口->{},预处理完成,耗时->{}秒",SwapAreaUtils.getSwapAreaData().getTransactionSn(),SwapAreaUtils.getSwapAreaData().getReqMethod(),(System.currentTimeMillis() - startTime)/1000.0);
	}
    /**
     * 解析请求参数
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param request 原始请求参数
     * @return BaseRequest
     * @throws IOException
     * @throws UnsupportedEncodingException
     * @since 2017年11月2日上午8:43:35
     */
	private BaseRequest parser(HttpServletRequest request) throws IOException, UnsupportedEncodingException {
		// 接口参数必须以utf-8编码
		String json = streamToString(request.getInputStream());
		return getBaseRequest(request, json);
	}
	/**
	 * 获取BaseRequest
	 * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/6/11 11:17
	 * @param request:原始请求参数
	 * @param json:JSON格式
	 * @return: com.jccfc.service.base.BaseRequest
	 */
	private BaseRequest getBaseRequest(HttpServletRequest request, String json) {
		String queryString = request.getQueryString();
		log.info("接口->{},原始POST请求参数:{},原始URL请求参数:{}", SwapAreaUtils.getSwapAreaData().getReqMethod(),json,queryString);
		if(StringUtils.isBlank(json)){
			throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_PARAM_PARSE_IS_EMPTY));
		}
		SwapAreaUtils.getSwapAreaData().setReqJson(json);
		// json解析
		BaseRequest baseRequest = null;
		try {
			baseRequest = FastJsonUtil.toBean(json, BaseRequest.class);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("解析接口请求参数异常,json:{}",json);
		}
		// json格式错误
		if (baseRequest == null) {
			throw new IllegalParameterException(Resources.getMessage(ErrorCodeConstants.COMMON_REQ_PARAM_PARSE_ERROR));
		}
		baseRequest.setQueryString(queryString);
		if(StringUtils.isNotBlank(request.getParameter(TIMESTAMP_KEY)) && StringUtils.isNumeric(request.getParameter(TIMESTAMP_KEY))){
			baseRequest.setTimestamp(Long.parseLong(request.getParameter(TIMESTAMP_KEY)));
		}

		return baseRequest;
	}

	/**
     * 后处理
     */
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		Long startTime = System.currentTimeMillis();
		//后置处理
        handlerStack.doAfterStack(SwapAreaUtils.getSwapAreaData().getBaseResponse());
		setResponse(response,SwapAreaUtils.getSwapAreaData().getBaseResponse());
        log.info("[{}]接口->{},后置处理完成,耗时->{}秒",SwapAreaUtils.getSwapAreaData().getTransactionSn(),SwapAreaUtils.getSwapAreaData().getReqMethod(),(System.currentTimeMillis() - startTime)/1000.0);
	}
	/**
	 * 返回處理
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		//后续处理
		afterHandle();
	}
	/**
	 * 异常处理
	 * <p>说明:</p>
	 * <li></li>
	 * @author DuanYong
	 * @param response 响应
	 * @param e 异常
	 * @throws IOException
	 * @since 2017年6月28日下午1:43:40
	 */
	private void handleException(HttpServletResponse response, Exception e) throws IOException {
		e.printStackTrace();
        log.error(Resources.getMessage(ErrorCodeConstants.COMMON_EXCEPTION_HEAD), WebUtil.getSwapAreaData().getTransactionSn(),e);
		try{
			BaseResponse baseResponse = getErrorBaseResponse(e);
			setResponse(response, baseResponse);
		}finally{
			//后续处理
			afterHandle();
		}
	}
	/**
	 * 获取异常返回
     * <li></li>
	 * @author duanyong@jccfc.com
	 * @date 2021/4/16 9:08
	 * @param e: 异常信息
	 * @return: com.javacoo.xservice.base.BaseResponse
	 */
	private BaseResponse getErrorBaseResponse(Exception e){
        BaseResponse baseResponse = new BaseResponse();
        if (e instanceof BaseException) {
            ((BaseException) e).handler(baseResponse);
        } else if (e instanceof IllegalArgumentException) {
            new IllegalParameterException(e.getMessage()).handler(baseResponse);
        }else{
            new IllegalParameterException().handler(baseResponse);
        }
        //设置响应数据
        SwapAreaUtils.getSwapAreaData().setEndTime(System.currentTimeMillis());
        SwapAreaUtils.getSwapAreaData().setBaseResponse(baseResponse);
        return baseResponse;
    }
    /**
     * 设置响应数据
     * <p>说明:</p>
     * <li></li>
     * @author DuanYong
     * @param response HttpServletResponse
     * @param baseResponse 响应数据
     * @throws IOException
     * @since 2017年7月12日上午8:37:02
     */
	private void setResponse(HttpServletResponse response, BaseResponse baseResponse) throws IOException {
		response.setStatus(HttpServletResponse.SC_OK);
		response.setContentType("application/json;charset=UTF-8");
		response.getWriter().write(FastJsonUtil.toJSONString(baseResponse));
	}
	/**
	 * 初始化数据交换区
	 * <p>说明:</p>
	 * <li></li>
	 * @param request 请求对象
	 * @author DuanYong
	 * @since 2017年6月30日下午1:56:21
	 */
	private void initSwapArea(HttpServletRequest request) {
		SwapAreaData swapAreaData = new SwapAreaData();
		swapAreaData.setReqUrl(request.getRequestURL().toString());
		swapAreaData.setReqIp(WebUtil.getHost(request));
		swapAreaData.setReqSign(request.getParameter(SIGN_KEY));
		swapAreaData.setStartTime(System.currentTimeMillis());
		swapAreaData.setTransactionSn(WebUtil.genTransSn());
		SwapAreaUtils.setSwapAreaData(swapAreaData);
	}
	/**
	 * 后续处理
	 * <p>说明:</p>
	 * <li>异步发布交易完成事件</li>
	 * <li>释放当前线程数据交换区数据</li>
	 * @author DuanYong
	 * @since 2017年6月30日下午1:59:16
	 */
	private void afterHandle() {
		//异步发布交易完成事件
		ApplicationContextProvider.getApplicationContext().publishEvent(new TransCompleteEvent(this,SwapAreaUtils.getSwapAreaData()));
		//释放当前线程数据交换区数据
		SwapAreaUtils.releaseCurrentSwapArea();
	}
}
